/* SPDX-License-Identifier: MIT */
/*
 * Copyright © 2020 Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "config.h"

#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/input.h>
#include "util-io.h"
#include "util-munit.h"
#include "util-memfile.h"
#include "util-memmap.h"

#include "eierpecken.h"

#if HAVE_MEMFD_CREATE
DEFINE_UNREF_CLEANUP_FUNC(memfile);
DEFINE_UNREF_CLEANUP_FUNC(memmap);
#endif

MUNIT_TEST(test_ei_device_basics)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		_unref_(eis_device) *device = eis_seat_new_device(seat);

		/* The default value */
		munit_assert_int(eis_device_get_type(device), ==, EIS_DEVICE_TYPE_VIRTUAL);

		eis_device_configure_name(device, "string is freed");
		munit_assert_string_equal(eis_device_get_name(device), "string is freed");

		/* overwrite before eis_device_add() is possible */
		eis_device_configure_name(device, __func__);
		munit_assert_string_equal(eis_device_get_name(device), __func__);

		munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER));
		munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD));
		munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE));
		munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH));

		eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER);
		munit_assert_true(eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER));
		eis_device_configure_capability(device, EIS_DEVICE_CAP_KEYBOARD);
		munit_assert_true(eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD));

		eis_device_add(device);

		/* device is read-only now */
		eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE);
		munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE));

		eis_device_configure_name(device, "nope");
		munit_assert_string_equal(eis_device_get_name(device), __func__);
	}

	peck_dispatch_ei(peck);

	/* device creation and getters/setters test */
	with_client(peck) {
		_unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED);
		struct ei_device *device = ei_event_get_device(event);

		munit_assert_not_null(device);
		munit_assert_int(ei_device_get_type(device), ==, EI_DEVICE_TYPE_VIRTUAL);
		munit_assert_string_equal(ei_device_get_name(device), __func__);

		munit_assert_true(ei_device_has_capability(device, EI_DEVICE_CAP_POINTER));
		munit_assert_true(ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD));
		munit_assert_false(ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE));
		munit_assert_false(ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH));
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_passive_ei_device_type)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		_unref_(eis_device) *phys = eis_seat_new_device(seat);

		eis_device_configure_type(phys, EIS_DEVICE_TYPE_PHYSICAL);
		munit_assert_int(eis_device_get_type(phys), ==, EIS_DEVICE_TYPE_PHYSICAL);
		eis_device_configure_capability(phys, EIS_DEVICE_CAP_POINTER);
		eis_device_configure_size(phys, 100, 100);
		eis_device_add(phys);

		/* noop after add */
		eis_device_configure_type(phys, EIS_DEVICE_TYPE_VIRTUAL);

		_unref_(eis_device) *virt = eis_seat_new_device(seat);
		eis_device_configure_type(virt, EIS_DEVICE_TYPE_VIRTUAL);
		eis_device_configure_capability(virt, EIS_DEVICE_CAP_POINTER);
		with_nonfatal_eis_bug(peck)
			eis_device_configure_size(virt, 200, 200);  /* Has no effect on a virtual device */
		munit_assert_int(eis_device_get_type(virt), ==, EIS_DEVICE_TYPE_VIRTUAL);
		eis_device_add(virt);

		/* noop after add */
		eis_device_configure_type(virt, EIS_DEVICE_TYPE_PHYSICAL);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *event_phys = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED);
		struct ei_device *phys = ei_event_get_device(event_phys);
		munit_assert_int(ei_device_get_type(phys), ==, EI_DEVICE_TYPE_PHYSICAL);
		munit_assert_int(ei_device_get_width(phys), ==, 100);
		munit_assert_int(ei_device_get_height(phys), ==, 100);

		_unref_(ei_event) *event_virt = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED);
		struct ei_device *virt = ei_event_get_device(event_virt);
		munit_assert_int(ei_device_get_type(virt), ==, EI_DEVICE_TYPE_VIRTUAL);
		munit_assert_int(ei_device_get_width(virt), ==, 0);
		munit_assert_int(ei_device_get_height(virt), ==, 0);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_set_name_multiple_devices)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT);
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		_unref_(eis_device) *d1 = eis_seat_new_device(seat);
		eis_device_configure_name(d1, "first device");
		eis_device_configure_capability(d1, EIS_DEVICE_CAP_POINTER);
		eis_device_add(d1);

		_unref_(eis_device) *d2 = eis_seat_new_device(seat);
		/* Unnamed */
		eis_device_configure_capability(d2, EIS_DEVICE_CAP_POINTER);
		eis_device_add(d2);

		_unref_(eis_device) *d3 = eis_seat_new_device(seat);
		eis_device_configure_name(d3, "third device");
		eis_device_configure_capability(d3, EIS_DEVICE_CAP_POINTER);
		eis_device_add(d3);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *e1 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED);
		struct ei_device *d1 = ei_event_get_device(e1);
		munit_assert_string_equal(ei_device_get_name(d1), "first device");

		_unref_(ei_event) *e2 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED);
		struct ei_device *d2 = ei_event_get_device(e2);
		munit_assert_string_equal(ei_device_get_name(d2), "unnamed device");

		_unref_(ei_event) *e3 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED);
		struct ei_device *d3 = ei_event_get_device(e3);
		munit_assert_string_equal(ei_device_get_name(d3), "third device");
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_never_added)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_dispatch_until_stable(peck);

	/* Unref after remove */
	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		struct eis_device *device = eis_seat_new_device(seat);
		eis_device_remove(device);
		eis_device_unref(device);
	}

	peck_dispatch_until_stable(peck);

	/* device was never added, shouldn't show up */
	with_client(peck) {
		peck_assert_no_ei_events(ei);
	}

	/* unref before remove.
	 *
	 * This would be invalid client code since you can't expect to have
	 * a ref after unref, but since we know the device is still here, we
	 * can test for the lib being correct.
	 */
	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		struct eis_device *device = eis_seat_new_device(seat);
		eis_device_unref(device);
		eis_device_remove(device);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		peck_assert_no_ei_events(ei);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_add_remove)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT);
	peck_dispatch_until_stable(peck);

	_unref_(ei_device) *device = NULL;
	_unref_(eis_device) *eis_device = NULL;

	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		eis_device = eis_seat_new_device(seat);
		eis_device_configure_name(eis_device, __func__);
		eis_device_configure_capability(eis_device, EIS_DEVICE_CAP_POINTER);
		eis_device_add(eis_device);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *event =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED);
		device = ei_device_ref(ei_event_get_device(event));
	}

	with_server(peck) {
		eis_device_remove(eis_device);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *event =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED);

		munit_assert_ptr_equal(ei_event_get_device(event), device);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_close)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_DEVICES);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_SYNC);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_pointer(peck);
		ei_device_close(device);

		device = peck_ei_get_default_touch(peck);
		ei_device_close(device);

		device = peck_ei_get_default_keyboard(peck);
		ei_device_close(device);

		device = peck_ei_get_default_pointer_absolute(peck);
		ei_device_close(device);

		peck_assert_no_ei_events(ei);
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *e1 =
			peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED);
		struct eis_device *d1 = eis_event_get_device(e1);
		munit_assert_ptr_equal(d1, peck_eis_get_default_pointer(peck));

		_unref_(eis_event) *e2 =
			peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED);
		struct eis_device *d2 = eis_event_get_device(e2);
		munit_assert_ptr_equal(d2, peck_eis_get_default_touch(peck));

		_unref_(eis_event) *e3 =
			peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED);
		struct eis_device *d3 = eis_event_get_device(e3);
		munit_assert_ptr_equal(d3, peck_eis_get_default_keyboard(peck));

		_unref_(eis_event) *e4 =
			peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED);
		struct eis_device *d4 = eis_event_get_device(e4);
		munit_assert_ptr_equal(d4, peck_eis_get_default_pointer_absolute(peck));

		/* release in different order */
		eis_device_remove(d2);
		eis_device_remove(d1);
		eis_device_remove(d4);
		eis_device_remove(d3);

		peck_assert_no_eis_events(eis);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *e1 =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED);
		struct ei_device *d1 = ei_event_get_device(e1);
		munit_assert_ptr_equal(d1, peck_ei_get_default_touch(peck));

		_unref_(ei_event) *e2 =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED);
		struct ei_device *d2 = ei_event_get_device(e2);
		munit_assert_ptr_equal(d2, peck_ei_get_default_pointer(peck));

		_unref_(ei_event) *e3 =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED);
		struct ei_device *d3 = ei_event_get_device(e3);
		munit_assert_ptr_equal(d3, peck_ei_get_default_pointer_absolute(peck));

		_unref_(ei_event) *e4 =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED);
		struct ei_device *d4 = ei_event_get_device(e4);
		munit_assert_ptr_equal(d4, peck_ei_get_default_keyboard(peck));

		peck_assert_no_ei_events(ei);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_button_button)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_pointer(peck);
		ei_device_button_button(device, BTN_LEFT, true);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_button_button(device, BTN_RIGHT, true);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_button_button(device, BTN_RIGHT, false);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_button_button(device, BTN_LEFT, false);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *ld =
			peck_eis_next_event(eis, EIS_EVENT_BUTTON_BUTTON);
		munit_assert_int(eis_event_button_get_button(ld), ==, BTN_LEFT);
		munit_assert_true(eis_event_button_get_is_press(ld));

		_unref_(eis_event) *rd =
			peck_eis_next_event(eis, EIS_EVENT_BUTTON_BUTTON);
		munit_assert_int(eis_event_button_get_button(rd), ==, BTN_RIGHT);
		munit_assert_true(eis_event_button_get_is_press(rd));

		_unref_(eis_event) *ru =
			peck_eis_next_event(eis, EIS_EVENT_BUTTON_BUTTON);
		munit_assert_int(eis_event_button_get_button(ru), ==, BTN_RIGHT);
		munit_assert_false(eis_event_button_get_is_press(ru));

		_unref_(eis_event) *lu =
			peck_eis_next_event(eis, EIS_EVENT_BUTTON_BUTTON);
		munit_assert_int(eis_event_button_get_button(lu), ==, BTN_LEFT);
		munit_assert_false(eis_event_button_get_is_press(lu));

	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_keyboard_key)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_keyboard(peck);
		ei_device_keyboard_key(device, KEY_Q, true);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_keyboard_key(device, KEY_Q, false);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *press =
			peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY);
		munit_assert_int(eis_event_keyboard_get_key(press), ==, KEY_Q);
		munit_assert_true(eis_event_keyboard_get_key_is_press(press));

		_unref_(eis_event) *release =
			peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY);
		munit_assert_int(eis_event_keyboard_get_key(release), ==, KEY_Q);
		munit_assert_false(eis_event_keyboard_get_key_is_press(release));
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_pointer_rel)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_pointer(peck);
		ei_device_pointer_motion(device, 1, 2);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_pointer_motion(device, 0.3, 1.4);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_pointer_motion(device, 100, 200);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *first =
			peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION);
		munit_assert_double_equal(eis_event_pointer_get_dx(first), 1.0, 2 /* precision */);
		munit_assert_double_equal(eis_event_pointer_get_dy(first), 2.0, 2 /* precision */);

		_unref_(eis_event) *second =
			peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION);
		munit_assert_double_equal(eis_event_pointer_get_dx(second), 0.3, 2 /* precision */);
		munit_assert_double_equal(eis_event_pointer_get_dy(second), 1.4, 2 /* precision */);

		_unref_(eis_event) *third =
			peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION);
		munit_assert_double_equal(eis_event_pointer_get_dx(third), 100, 2 /* precision */);
		munit_assert_double_equal(eis_event_pointer_get_dy(third), 200, 2 /* precision */);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_regions)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		_unref_(eis_device) *device = eis_seat_new_device(seat);
		eis_device_configure_name(device, __func__);
		eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE);

		/* nothing cares about the actual values, so we're just
		 * checking for correct passthrough here */
		_unref_(eis_region) *r1 = eis_device_new_region(device);
		eis_region_set_size(r1, 100, 200);
		eis_region_set_offset(r1, 300, 400);
		eis_region_set_mapping_id(r1, "oo oo eye dee");
		/* no scale, default to 1.0 */
		eis_region_add(r1);

		_unref_(eis_region) *r2 = eis_device_new_region(device);
		eis_region_set_size(r2, 500, 600);
		eis_region_set_offset(r2, 700, 800);
		eis_region_set_physical_scale(r2, 3.9);
		eis_region_add(r2);

		_unref_(eis_region) *r3 = eis_device_new_region(device);
		eis_region_set_size(r3, 900, 1000);
		eis_region_set_offset(r3, 1100, 1200);
		eis_region_set_physical_scale(r3, 0.3);
		eis_region_add(r3);

		/* Add the same region twice, should be ignored */
		eis_region_add(r3);
		eis_region_add(r3);
		eis_region_add(r3);

		eis_device_add(device);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_pointer_absolute(peck);
		struct ei_region *r, *r2;

		r = ei_device_get_region(device, 0);
		munit_assert_int(ei_region_get_width(r), ==, 100);
		munit_assert_int(ei_region_get_height(r), ==, 200);
		munit_assert_int(ei_region_get_x(r), ==, 300);
		munit_assert_int(ei_region_get_y(r), ==, 400);
		munit_assert_double_equal(ei_region_get_physical_scale(r), 1.0, 2 /* precision */);
		munit_assert_string_equal(ei_region_get_mapping_id(r), "oo oo eye dee");

		r2 = ei_device_get_region_at(device, 300, 400);
		munit_assert_ptr_equal(r, r2);
		r2 = ei_device_get_region_at(device, 350, 450);
		munit_assert_ptr_equal(r, r2);

		r = ei_device_get_region(device, 1);
		munit_assert_int(ei_region_get_width(r), ==, 500);
		munit_assert_int(ei_region_get_height(r), ==, 600);
		munit_assert_int(ei_region_get_x(r), ==, 700);
		munit_assert_int(ei_region_get_y(r), ==, 800);
		munit_assert_double_equal(ei_region_get_physical_scale(r), 3.9, 2 /* precision */);
		munit_assert_null(ei_region_get_mapping_id(r));

		r2 = ei_device_get_region_at(device, 750, 850);
		munit_assert_ptr_equal(r, r2);

		r = ei_device_get_region(device, 2);
		munit_assert_int(ei_region_get_width(r), ==, 900);
		munit_assert_int(ei_region_get_height(r), ==, 1000);
		munit_assert_int(ei_region_get_x(r), ==, 1100);
		munit_assert_int(ei_region_get_y(r), ==, 1200);
		munit_assert_double_equal(ei_region_get_physical_scale(r), 0.3, 2 /* precision */);
		munit_assert_null(ei_region_get_mapping_id(r));

		r2 = ei_device_get_region_at(device, 1999, 2199);
		munit_assert_ptr_equal(r, r2);
		munit_assert_ptr_null(ei_device_get_region_at(device, 2000, 2200)); /* at the edge */

		munit_assert_ptr_null(ei_device_get_region(device, 3));
		munit_assert_ptr_null(ei_device_get_region_at(device, 0, 100));
	}

	return MUNIT_OK;
}

/* Same test as test_ei_device_regions_touch but for absmotion */
MUNIT_TEST(test_ei_device_regions_absmotion)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		_unref_(eis_device) *device = eis_seat_new_device(seat);
		eis_device_configure_name(device, __func__);
		eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE);

		/* nothing cares about the actual values, so we're just
		 * checking for correct passthrough here */
		_unref_(eis_region) *r1 = eis_device_new_region(device);
		eis_region_set_size(r1, 100, 200);
		eis_region_set_offset(r1, 300, 400);
		eis_region_add(r1);

		_unref_(eis_region) *r2 = eis_device_new_region(device);
		eis_region_set_size(r2, 500, 600);
		eis_region_set_offset(r2, 700, 800);
		eis_region_add(r2);

		_unref_(eis_region) *r3 = eis_device_new_region(device);
		eis_region_set_size(r3, 900, 1000);
		eis_region_set_offset(r3, 1100, 1200);
		eis_region_add(r3);

		eis_device_add(device);
		eis_device_resume(device);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_pointer_absolute(peck);
		ei_device_start_emulating(device, 0);
		/* valid */
		ei_device_pointer_motion_absolute(device, 301, 401);
		ei_device_frame(device, peck_ei_now(peck));

		/* ignored */
		ei_device_pointer_motion_absolute(device, 401, 401);
		ei_device_frame(device, peck_ei_now(peck));

		/* valid */
		ei_device_pointer_motion_absolute(device, 701, 801);
		ei_device_frame(device, peck_ei_now(peck));

		/* ignored */
		ei_device_pointer_motion_absolute(device, 1201, 801);
		ei_device_frame(device, peck_ei_now(peck));

		/* valid */
		ei_device_pointer_motion_absolute(device, 1101, 1201);
		ei_device_frame(device, peck_ei_now(peck));

		/* ignored */
		ei_device_pointer_motion_absolute(device, 2000, 1201);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *e1 = peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION_ABSOLUTE);
		munit_assert_double_equal(eis_event_pointer_get_absolute_x(e1), 301, 2 /* precision */);
		munit_assert_double_equal(eis_event_pointer_get_absolute_y(e1), 401, 2 /* precision */);

		_unref_(eis_event) *e2 = peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION_ABSOLUTE);
		munit_assert_double_equal(eis_event_pointer_get_absolute_x(e2), 701, 2 /* precision */);
		munit_assert_double_equal(eis_event_pointer_get_absolute_y(e2), 801, 2 /* precision */);

		_unref_(eis_event) *e3 = peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION_ABSOLUTE);
		munit_assert_double_equal(eis_event_pointer_get_absolute_x(e3), 1101, 2 /* precision */);
		munit_assert_double_equal(eis_event_pointer_get_absolute_y(e3), 1201, 2 /* precision */);

		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

/* Same test as test_ei_device_regions_absmotion but for touch */
MUNIT_TEST(test_ei_device_regions_touch)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		_unref_(eis_device) *device = eis_seat_new_device(seat);
		eis_device_configure_name(device, __func__);
		eis_device_configure_capability(device, EIS_DEVICE_CAP_TOUCH);

		/* nothing cares about the actual values, so we're just
		 * checking for correct passthrough here */
		_unref_(eis_region) *r1 = eis_device_new_region(device);
		eis_region_set_size(r1, 100, 200);
		eis_region_set_offset(r1, 300, 400);
		eis_region_add(r1);

		_unref_(eis_region) *r2 = eis_device_new_region(device);
		eis_region_set_size(r2, 500, 600);
		eis_region_set_offset(r2, 700, 800);
		eis_region_add(r2);

		_unref_(eis_region) *r3 = eis_device_new_region(device);
		eis_region_set_size(r3, 900, 1000);
		eis_region_set_offset(r3, 1100, 1200);
		eis_region_add(r3);

		eis_device_add(device);
		eis_device_resume(device);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_touch(peck);
		ei_device_start_emulating(device, 0);

		/* valid */
		_unref_(ei_touch) *t1 = ei_device_touch_new(device);
		ei_touch_down(t1, 301, 401);
		ei_touch_up(t1);
		ei_device_frame(device, peck_ei_now(peck));

		/* ignored */
		_unref_(ei_touch) *t2 = ei_device_touch_new(device);
		peck_ei_disable_fatal_bug(peck);
		ei_touch_down(t2, 401, 401);
		ei_touch_up(t2);
		peck_ei_enable_fatal_bug(peck);
		ei_device_frame(device, peck_ei_now(peck));

		/* valid */
		_unref_(ei_touch) *t3 = ei_device_touch_new(device);
		ei_touch_down(t3, 701, 801);
		ei_touch_up(t3);
		ei_device_frame(device, peck_ei_now(peck));

		/* ignored */
		_unref_(ei_touch) *t4 = ei_device_touch_new(device);
		peck_ei_disable_fatal_bug(peck);
		ei_touch_down(t4, 1201, 801);
		ei_touch_up(t4);
		peck_ei_enable_fatal_bug(peck);
		ei_device_frame(device, peck_ei_now(peck));

		/* valid */
		_unref_(ei_touch) *t5 = ei_device_touch_new(device);
		ei_touch_down(t5, 1101, 1201);
		ei_touch_up(t5);
		ei_device_frame(device, peck_ei_now(peck));

		/* ignored */
		_unref_(ei_touch) *t6 = ei_device_touch_new(device);
		peck_ei_disable_fatal_bug(peck);
		ei_touch_down(t6, 2000, 1201);
		ei_touch_up(t6);
		peck_ei_enable_fatal_bug(peck);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *t1d = peck_eis_next_event(eis, EIS_EVENT_TOUCH_DOWN);
		munit_assert_double_equal(eis_event_touch_get_x(t1d), 301, 2 /* precision */);
		munit_assert_double_equal(eis_event_touch_get_y(t1d), 401, 2 /* precision */);

		_unref_(eis_event) *t1u = peck_eis_next_event(eis, EIS_EVENT_TOUCH_UP);
		munit_assert_ptr_not_null(t1u);

		_unref_(eis_event) *t2d = peck_eis_next_event(eis, EIS_EVENT_TOUCH_DOWN);
		munit_assert_double_equal(eis_event_touch_get_x(t2d), 701, 2 /* precision */);
		munit_assert_double_equal(eis_event_touch_get_y(t2d), 801, 2 /* precision */);

		_unref_(eis_event) *t2u = peck_eis_next_event(eis, EIS_EVENT_TOUCH_UP);
		munit_assert_ptr_not_null(t2u);

		_unref_(eis_event) *t3d = peck_eis_next_event(eis, EIS_EVENT_TOUCH_DOWN);
		munit_assert_double_equal(eis_event_touch_get_x(t3d), 1101, 2 /* precision */);
		munit_assert_double_equal(eis_event_touch_get_y(t3d), 1201, 2 /* precision */);

		_unref_(eis_event) *t3u = peck_eis_next_event(eis, EIS_EVENT_TOUCH_UP);
		munit_assert_ptr_not_null(t3u);

		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_regions_ref_unref)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		_unref_(eis_device) *device = eis_seat_new_device(seat);
		eis_device_configure_name(device, __func__);
		eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE);

		/* region never added */
		struct eis_region *r1 = eis_device_new_region(device);
		eis_region_unref(r1);
		eis_device_add(device);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_pointer_absolute(peck);
		munit_assert_ptr_null(ei_device_get_region(device, 0));
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_regions_late_add)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		_unref_(eis_device) *device = eis_seat_new_device(seat);
		eis_device_configure_name(device, __func__);
		eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE);

		eis_device_add(device);

		/* region added after device_add -> ignored */
		_unref_(eis_region) *r1 = eis_device_new_region(device);
		eis_region_set_size(r1, 100, 200);

		/* Bug: eis_region_add: device already (dis)connected */
		with_nonfatal_eis_bug(peck)
			eis_region_add(r1);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_pointer_absolute(peck);
		munit_assert_ptr_null(ei_device_get_region(device, 0));
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_pointer_abs)
{
	_unref_(peck) *peck = peck_new();
	struct ei_device *device = NULL;

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		device = peck_ei_get_default_pointer_absolute(peck);

		for (int i = 0; i < 10; i++) {
			ei_device_pointer_motion_absolute(device, 1 * i , 2 + i);
			ei_device_frame(device, peck_ei_now(peck));
		}
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		for (int i = 0; i < 10; i++) {
			_unref_(eis_event) *e =
				peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION_ABSOLUTE);
			munit_assert_double_equal(eis_event_pointer_get_absolute_x(e), 1.0 * i, 2 /* precision */);
			munit_assert_double_equal(eis_event_pointer_get_absolute_y(e), 2.0 + i, 2 /* precision */);
		}
		peck_assert_no_eis_events(eis);
	}

	with_client(peck) {
		/* We know our default device has one region */
		struct ei_region *r = ei_device_get_region(device, 0);
		uint32_t maxx = ei_region_get_x(r) + ei_region_get_width(r);
		uint32_t maxy = ei_region_get_y(r) + ei_region_get_height(r);

		/* outside of pointer range, expect to be discarded */
		ei_device_pointer_motion_absolute(device, maxx + 1, maxy/2);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_pointer_motion_absolute(device, maxx/2 , maxy + 1);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		peck_assert_no_eis_events(eis);

		/* Don't auto-handle the DEVICE_CLOSED event */
		peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
		peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	}

	with_client(peck) {
		ei_device_close(device);
		/* absmotion after remove must not trigger an event */
		with_nonfatal_ei_bug(peck)
			ei_device_pointer_motion_absolute(device, 100, 200);
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *stop =
			peck_eis_next_event(eis, EIS_EVENT_DEVICE_STOP_EMULATING);
		_unref_(eis_event) *closed =
			peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED);
		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_scroll)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_pointer(peck);
		ei_device_scroll_delta(device, 1.1, 2.2);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_scroll_discrete(device, 3, 4);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *first =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA);
		munit_assert_double_equal(eis_event_scroll_get_dx(first), 1.1, 2 /* precision */);
		munit_assert_double_equal(eis_event_scroll_get_dy(first), 2.2, 2 /* precision */);

		_unref_(eis_event) *second =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_DISCRETE);
		munit_assert_int(eis_event_scroll_get_discrete_dx(second), ==, 3);
		munit_assert_int(eis_event_scroll_get_discrete_dy(second), ==, 4);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_scroll_stop)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_pointer(peck);
		ei_device_scroll_delta(device, 1.1, 2.2);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_scroll_stop(device, true, false);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_scroll_stop(device, false, true);
		ei_device_frame(device, peck_ei_now(peck));

		/* This should not generate an event */
		ei_device_scroll_stop(device, true, true);
		ei_device_frame(device, peck_ei_now(peck));

		/* But scrolling again will re-enable stopping */
		ei_device_scroll_delta(device, 3.3, 4.4);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_scroll_stop(device, true, true);
		ei_device_frame(device, peck_ei_now(peck));

		ei_device_scroll_delta(device, 3.3, 4.4);
		/* This one is a client bug and shouldn't trigger an event */
		ei_device_scroll_stop(device, false, false);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *scroll =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA);

		_unref_(eis_event) *first =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_STOP);
		munit_assert(eis_event_scroll_get_stop_x(first));
		munit_assert(!eis_event_scroll_get_stop_y(first));

		_unref_(eis_event) *second =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_STOP);
		munit_assert(!eis_event_scroll_get_stop_x(second));
		munit_assert(eis_event_scroll_get_stop_y(second));

		/* third one doesn't trigger an event */

		_unref_(eis_event) *again =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA);

		_unref_(eis_event) *fourth =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_STOP);
		munit_assert(eis_event_scroll_get_stop_x(fourth));
		munit_assert(eis_event_scroll_get_stop_y(fourth));

		_unref_(eis_event) *again_again =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA);

		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_scroll_cancel)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_pointer(peck);
		ei_device_scroll_delta(device, 1.1, 2.2);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_scroll_cancel(device, true, false);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_scroll_cancel(device, false, true);
		ei_device_frame(device, peck_ei_now(peck));

		/* This should not generate an event */
		ei_device_scroll_cancel(device, true, true);
		ei_device_frame(device, peck_ei_now(peck));

		/* But scrolling again will re-enable stopping */
		ei_device_scroll_delta(device, 3.3, 4.4);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_scroll_cancel(device, true, true);
		ei_device_frame(device, peck_ei_now(peck));

		ei_device_scroll_delta(device, 3.3, 4.4);
		/* This one is a client bug and shouldn't trigger an event */
		ei_device_scroll_cancel(device, false, false);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *scroll =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA);

		_unref_(eis_event) *first =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_CANCEL);
		munit_assert(eis_event_scroll_get_stop_x(first));
		munit_assert(!eis_event_scroll_get_stop_y(first));

		_unref_(eis_event) *second =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_CANCEL);
		munit_assert(!eis_event_scroll_get_stop_x(second));
		munit_assert(eis_event_scroll_get_stop_y(second));

		/* third one doesn't trigger an event */

		_unref_(eis_event) *again =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA);

		_unref_(eis_event) *fourth =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_CANCEL);
		munit_assert(eis_event_scroll_get_stop_x(fourth));
		munit_assert(eis_event_scroll_get_stop_y(fourth));

		_unref_(eis_event) *again_again =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA);

		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_scroll_stop_cancel)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	/* cancel after stop is fine, stop after cancel is ignored */
	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_pointer(peck);
		ei_device_scroll_delta(device, 1.1, 2.2);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_scroll_stop(device, true, false);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_scroll_cancel(device, true, false);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_scroll_cancel(device, false, true);
		ei_device_frame(device, peck_ei_now(peck));

		/* This should not generate an event */
		ei_device_scroll_stop(device, true, true);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *scroll =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA);

		_unref_(eis_event) *stop =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_STOP);
		munit_assert(eis_event_scroll_get_stop_x(stop));
		munit_assert(!eis_event_scroll_get_stop_y(stop));

		_unref_(eis_event) *first =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_CANCEL);
		munit_assert(eis_event_scroll_get_stop_x(first));
		munit_assert(!eis_event_scroll_get_stop_y(first));

		_unref_(eis_event) *second =
			peck_eis_next_event(eis, EIS_EVENT_SCROLL_CANCEL);
		munit_assert(!eis_event_scroll_get_stop_x(second));
		munit_assert(eis_event_scroll_get_stop_y(second));

		/* third one doesn't trigger an event */
		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_touch)
{
	_unref_(peck) *peck = peck_new();
	struct ei_device *device = NULL;
	uint32_t maxx = 0, maxy = 0;

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		device = peck_ei_get_default_touch(peck);
		/* We know our default device has one region */

		struct ei_region *r = ei_device_get_region(device, 0);
		maxx = ei_region_get_x(r) + ei_region_get_width(r);
		maxy = ei_region_get_y(r) + ei_region_get_height(r);

		_unref_(ei_touch) *t = ei_device_touch_new(device);
		ei_touch_down(t, 1, 2);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_motion(t, 200, 500);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_up(t);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *down = peck_eis_touch_down(eis, 1, 2);
		uint32_t tid = eis_event_touch_get_id(down);

		_unref_(eis_event) *motion = peck_eis_touch_motion(eis, 200, 500);
		munit_assert_uint32(eis_event_touch_get_id(motion), ==, tid);

		_unref_(eis_event) *up = peck_eis_touch_up(eis);
		munit_assert_uint32(eis_event_touch_get_id(up), ==, tid);

		peck_assert_no_eis_events(eis);
	}

	with_client(peck) {
		_unref_(ei_touch) *t = ei_device_touch_new(device);
		/* outside clip range, expect touch to be dropped */
		with_nonfatal_ei_bug(peck) {
			ei_touch_down(t, maxx + 1, maxy/2);
			ei_device_frame(device, peck_ei_now(peck));
			/* ignored because the touch down was out of range */
			ei_touch_motion(t, maxx + 1, maxy/3);
			ei_device_frame(device, peck_ei_now(peck));
			/* ignored because the touch down was out of range */
			ei_touch_up(t);
		}
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_touch) *t = ei_device_touch_new(device);
		/* outside clip range, expect touch to be dropped */
		with_nonfatal_ei_bug(peck) {
			ei_touch_down(t, maxx/2, maxy + 1);
			ei_device_frame(device, peck_ei_now(peck));
			/* ignored because the touch down was out of range */
			ei_touch_motion(t, maxx/3, maxy + 1);
			ei_device_frame(device, peck_ei_now(peck));
			/* ignored because the touch down was out of range */
			ei_touch_up(t);
		}
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		peck_assert_no_eis_events(eis);
	}

	with_client(peck) {
		_unref_(ei_touch) *t = ei_device_touch_new(device);
		ei_touch_down(t, 100, 200);
		ei_device_frame(device, peck_ei_now(peck));
		/* outside allowed range, generates a touch up */
		with_nonfatal_ei_bug(peck)
			ei_touch_motion(t, maxx + 1, 200);
		ei_device_frame(device, peck_ei_now(peck));
		/* touch is already considered up */
		with_nonfatal_ei_bug(peck)
			ei_touch_up(t);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 200);
		_unref_(eis_event) *up = peck_eis_touch_up(eis);
		peck_assert_no_eis_events(eis);
	}

	with_client(peck) {
		_unref_(ei_touch) *t = ei_device_touch_new(device);
		ei_touch_down(t, 100, 100);
		ei_device_frame(device, peck_ei_now(peck));
		/* client forgets to touch up, touch_unref takes care of it */
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 100);
		_unref_(eis_event) *up = peck_eis_touch_up(eis);
		peck_assert_no_eis_events(eis);
	}

	with_client(peck) {
		/* touch only allocated, not actually set down */
		_unref_(ei_touch) *t1 = ei_device_touch_new(device);

		/* touch never set down */
		_unref_(ei_touch) *t2 = ei_device_touch_new(device);
		with_nonfatal_ei_bug(peck)
			ei_touch_up(t2);
		ei_device_frame(device, peck_ei_now(peck));

		/* touch never set down */
		_unref_(ei_touch) *t3 = ei_device_touch_new(device);
		with_nonfatal_ei_bug(peck)
			ei_touch_motion(t3, 100, 200);
		ei_device_frame(device, peck_ei_now(peck));
		with_nonfatal_ei_bug(peck)
			ei_touch_up(t3);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);
	with_server(peck) {
		peck_assert_no_eis_events(eis);
	}

	with_client(peck) {
		/* touch re-used */
		_unref_(ei_touch) *t = ei_device_touch_new(device);
		ei_touch_down(t, 100, 200);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_up(t);
		ei_device_frame(device, peck_ei_now(peck));
		with_nonfatal_ei_bug(peck)
			ei_touch_down(t, 200, 300);
		ei_device_frame(device, peck_ei_now(peck));
		with_nonfatal_ei_bug(peck)
			ei_touch_motion(t, 300, 400);
		ei_device_frame(device, peck_ei_now(peck));
		with_nonfatal_ei_bug(peck)
			ei_touch_up(t);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 200);
		_unref_(eis_event) *up = peck_eis_touch_up(eis);
		peck_assert_no_eis_events(eis);
	}

	with_client(peck) {
		/* double-down, double-up */
		_unref_(ei_touch) *t = ei_device_touch_new(device);
		ei_touch_down(t, 100, 200);
		ei_device_frame(device, peck_ei_now(peck));
		with_nonfatal_ei_bug(peck)
			ei_touch_down(t, 200, 300); /* ignored */
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_motion(t, 300, 400);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_up(t);
		ei_device_frame(device, peck_ei_now(peck));
		with_nonfatal_ei_bug(peck)
			ei_touch_up(t); /* ignored */
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 200);
		_unref_(eis_event) *motion = peck_eis_touch_motion(eis, 300, 400);
		_unref_(eis_event) *up = peck_eis_touch_up(eis);
		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_touch_cancel)
{
	_unref_(peck) *peck = peck_new();
	struct ei_device *device = NULL;

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		device = peck_ei_get_default_touch(peck);
		/* We know our default device has one region */
		_unref_(ei_touch) *t = ei_device_touch_new(device);
		ei_touch_down(t, 1, 2);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_motion(t, 200, 500);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_cancel(t);
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *down = peck_eis_touch_down(eis, 1, 2);
		uint32_t tid = eis_event_touch_get_id(down);

		_unref_(eis_event) *motion = peck_eis_touch_motion(eis, 200, 500);
		munit_assert_uint32(eis_event_touch_get_id(motion), ==, tid);

		_unref_(eis_event) *up = peck_eis_touch_up(eis);
		munit_assert_uint32(eis_event_touch_get_id(up), ==, tid);
		munit_assert_true(eis_event_touch_get_is_cancel(up));

		peck_assert_no_eis_events(eis);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		/* up + cancel, latter is ignored */
		_unref_(ei_touch) *t = ei_device_touch_new(device);
		ei_touch_down(t, 100, 200);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_motion(t, 300, 400);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_up(t);
		with_nonfatal_ei_bug(peck)
			ei_touch_cancel(t); /* ignored */
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 200);
		_unref_(eis_event) *motion = peck_eis_touch_motion(eis, 300, 400);
		_unref_(eis_event) *up = peck_eis_touch_up(eis);
		munit_assert_false(eis_event_touch_get_is_cancel(up));
		peck_assert_no_eis_events(eis);
	}

	with_client(peck) {
		/* cancel + up, latter is ignored */
		_unref_(ei_touch) *t = ei_device_touch_new(device);
		ei_touch_down(t, 100, 200);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_motion(t, 300, 400);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_cancel(t);
		with_nonfatal_ei_bug(peck)
			ei_touch_up(t); /* ignored */
		ei_device_frame(device, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 200);
		_unref_(eis_event) *motion = peck_eis_touch_motion(eis, 300, 400);
		_unref_(eis_event) *up = peck_eis_touch_up(eis);
		munit_assert_true(eis_event_touch_get_is_cancel(up));
		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_multitouch)
{
	_unref_(peck) *peck = peck_new();
	struct ei_device *device = NULL;

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		device = peck_ei_get_default_touch(peck);
		_unref_(ei_touch) *t1 = ei_device_touch_new(device);
		_unref_(ei_touch) *t2 = ei_device_touch_new(device);
		ei_touch_down(t1, 1, 2);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_motion(t1, 2, 3);
		ei_device_frame(device, peck_ei_now(peck));

		ei_touch_down(t2, 3, 4);
		ei_device_frame(device, peck_ei_now(peck));
		ei_touch_motion(t2, 4, 5);
		ei_device_frame(device, peck_ei_now(peck));

		ei_touch_up(t2);
		ei_touch_up(t1);
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *down1 = peck_eis_touch_down(eis, 1, 2);
		uint32_t tid1 = eis_event_touch_get_id(down1);

		_unref_(eis_event) *motion1 = peck_eis_touch_motion(eis, 2, 3);
		munit_assert_uint32(eis_event_touch_get_id(motion1), ==, tid1);

		_unref_(eis_event) *down2 = peck_eis_touch_down(eis, 3, 4);
		uint32_t tid2 = eis_event_touch_get_id(down2);
		munit_assert_uint32(tid2, !=, tid1);

		_unref_(eis_event) *motion2 = peck_eis_touch_motion(eis, 4, 5);
		munit_assert_uint32(eis_event_touch_get_id(motion2), ==, tid2);

		_unref_(eis_event) *up2 = peck_eis_touch_up(eis);
		munit_assert_uint32(eis_event_touch_get_id(up2), ==, tid2);

		_unref_(eis_event) *up1 = peck_eis_touch_up(eis);
		munit_assert_uint32(eis_event_touch_get_id(up1), ==, tid1);
	}

	return MUNIT_OK;
}

#if HAVE_MEMFD_CREATE
MUNIT_TEST(test_ei_keymap_invalid)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		_unref_(eis_device) *device = eis_seat_new_device(seat);

		const char data[5] = {1, 2, 3, 4, 5};
		_unref_(memfile) *fd = memfile_new(data, sizeof(data));

		munit_assert_ptr_null(eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB + 1,
						     memfile_get_fd(fd), memfile_get_size(fd)));
		munit_assert_ptr_null(eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB - 1,
						     memfile_get_fd(fd), memfile_get_size(fd)));
		munit_assert_ptr_null(eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB,
						     -1, memfile_get_size(fd)));
		munit_assert_ptr_null(eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB,
						     memfile_get_fd(fd), 0));

		/* Valid keymap, valgrind checks only */
		_unref_(eis_keymap) *unused =
			eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB, memfile_get_fd(fd), memfile_get_size(fd));
		munit_assert_ptr_not_null(unused);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_keymap_set)
{
	_unref_(peck) *peck = peck_new();
	const char data[5] = {1, 2, 3, 4, 5};
	_unref_(memfile) *fd1 = memfile_new(data, sizeof(data));
	_unref_(eis_keymap) *keymap = NULL;

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT);
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_seat *seat = peck_eis_get_default_seat(peck);
		_unref_(eis_device) *device = eis_seat_new_device(seat);
		eis_device_configure_name(device, __func__);
		eis_device_configure_capability(device, EIS_DEVICE_CAP_KEYBOARD);

		keymap = eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB,
					memfile_get_fd(fd1), memfile_get_size(fd1));
		eis_keymap_add(keymap);
		munit_assert_ptr_equal(eis_device_keyboard_get_keymap(device), keymap);

		/* Not possible to overwrite a keymap on a device once it's set */
		_unref_(memfile) *fd2 = memfile_new(data, sizeof(data));
		_unref_(eis_keymap) *overwrite =
			eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB, memfile_get_fd(fd2), memfile_get_size(fd2));
		with_nonfatal_eis_bug(peck)
			eis_keymap_add(overwrite);
		munit_assert_ptr_equal(eis_device_keyboard_get_keymap(device), keymap);

		eis_device_add(device);

		/* Still impossible to overwrite after add */
		_unref_(eis_keymap) *ignored =
			eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB,
				       memfile_get_fd(fd2), memfile_get_size(fd2));
		with_nonfatal_eis_bug(peck)
			eis_keymap_add(ignored);
		munit_assert_ptr_equal(eis_device_keyboard_get_keymap(device), keymap);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *event =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED);
		struct ei_device *ei_device = ei_event_get_device(event);
		struct ei_keymap *ei_keymap = ei_device_keyboard_get_keymap(ei_device);

		int fd = ei_keymap_get_fd(ei_keymap);
		munit_assert_int(fd, !=, -1);
		munit_assert_uint(ei_keymap_get_size(ei_keymap), ==, memfile_get_size(fd1));
		munit_assert_uint(ei_keymap_get_type(ei_keymap), ==, EI_KEYMAP_TYPE_XKB);

		_unref_(memmap) *keymap = memmap_new(ei_keymap_get_fd(ei_keymap),
						     ei_keymap_get_size(ei_keymap));
		char *buf = memmap_get_data(keymap);
		munit_assert_true(memcmp(buf, data, memmap_get_size(keymap)) == 0);
		ei_device_close(ei_device);
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *event =
			peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED);
		struct eis_device *d = eis_event_get_device(event);

		/* Rejecting a device does not unset the keymap because
		 * you're not supposed to do anything with the device anyway */
		struct eis_keymap *km = eis_device_keyboard_get_keymap(d);
		munit_assert_ptr_equal(keymap, km);
	}

	return MUNIT_OK;
}
#endif

MUNIT_TEST(test_ei_keyboard_modifiers)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_device *kbd = peck_eis_get_default_keyboard(peck);
		eis_device_keyboard_send_xkb_modifiers(kbd, 0x1, 0x2, 0x4, 0x8);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *event =
			peck_ei_next_event(ei, EI_EVENT_KEYBOARD_MODIFIERS);

		uint32_t depressed, locked, latched, group;
		depressed = ei_event_keyboard_get_xkb_mods_depressed(event);
		latched = ei_event_keyboard_get_xkb_mods_latched(event);
		locked = ei_event_keyboard_get_xkb_mods_locked(event);
		group = ei_event_keyboard_get_xkb_group(event);

		munit_assert_uint(depressed, ==, 0x1);
		munit_assert_uint(latched, ==, 0x2);
		munit_assert_uint(locked, ==, 0x4);
		munit_assert_uint(group, ==, 0x8);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_frame_timestamp)
{
	_unref_(peck) *peck = peck_new();
	uint64_t ts1 = 0, ts2 = 0;

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		ts1 = peck_ei_now(peck);
		struct ei_device *device = peck_ei_get_default_keyboard(peck);
		ei_device_keyboard_key(device, KEY_Q, true);
		ei_device_frame(device, ts1);

		ts2 = peck_ei_now(peck);
		ei_device_keyboard_key(device, KEY_Q, false);
		ei_device_frame(device, ts2);
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *kbd1 =
			peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY);
		_unref_(eis_event) *frame1 =
			peck_eis_next_event(eis, EIS_EVENT_FRAME);
		_unref_(eis_event) *kbd2 =
			peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY);
		_unref_(eis_event) *frame2 =
			peck_eis_next_event(eis, EIS_EVENT_FRAME);

		uint64_t timestamp = eis_event_get_time(frame1);
		munit_assert_uint64(timestamp, ==, ts1);
		munit_assert_uint64(timestamp, ==, eis_event_get_time(kbd1));

		timestamp = eis_event_get_time(frame2);
		munit_assert_uint64(timestamp, ==, ts2);
		munit_assert_uint64(timestamp, ==, eis_event_get_time(kbd2));

		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_no_empty_frames)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_keyboard(peck);
		ei_device_frame(device, peck_ei_now(peck)); /* Expect to be filtered */
		ei_device_keyboard_key(device, KEY_Q, true);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_frame(device, peck_ei_now(peck)); /* Expect to be filtered */
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *kbd =
			peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY);
		_unref_(eis_event) *frame =
			peck_eis_next_event(eis, EIS_EVENT_FRAME);
		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_buffered_frame)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *kbd = peck_ei_get_default_keyboard(peck);
		ei_device_keyboard_key(kbd, KEY_Q, true);

		struct ei_device *pointer = peck_ei_get_default_pointer(peck);
		ei_device_pointer_motion(pointer, 1.0, 2.0);
	}

	peck_dispatch_until_stable(peck);

	/* No event visible yet, frame hasn't been sent */
	with_server(peck) {
		peck_assert_no_eis_events(eis);
	}

	/* Flush in inverse order to original events */
	with_client(peck) {
		struct ei_device *pointer = peck_ei_get_default_pointer(peck);
		ei_device_frame(pointer, peck_ei_now(peck));

		struct ei_device *keyboard = peck_ei_get_default_keyboard(peck);
		ei_device_frame(keyboard, peck_ei_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *rel =
			peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION);
		_unref_(eis_event) *frame1 =
			peck_eis_next_event(eis, EIS_EVENT_FRAME);

		_unref_(eis_event) *kbd =
			peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY);
		_unref_(eis_event) *frame2 =
			peck_eis_next_event(eis, EIS_EVENT_FRAME);

		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_flush_frame)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_keyboard(peck);
		ei_device_keyboard_key(device, KEY_Q, true);
		/* Missing call to ei_device_frame() */
		with_nonfatal_ei_bug(peck)
			ei_device_stop_emulating(device);
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_event) *kbd =
			peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY);
		_unref_(eis_event) *frame =
			peck_eis_next_event(eis, EIS_EVENT_FRAME);
		_unref_(eis_event) *stop =
			peck_eis_next_event(eis, EIS_EVENT_DEVICE_STOP_EMULATING);
		peck_assert_no_eis_events(eis);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_ei_device_remove_no_stop_emulating_event)
{
	_unref_(peck) *peck = peck_new();

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		struct ei_device *device = peck_ei_get_default_keyboard(peck);
		ei_device_keyboard_key(device, KEY_Q, true);
		ei_device_frame(device, peck_ei_now(peck));
		ei_device_keyboard_key(device, KEY_Q, false);
		ei_device_frame(device, peck_ei_now(peck));
	}
	peck_dispatch_until_stable(peck);

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_keyboard(peck);
		eis_device_remove(device);
	}
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *removed =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED);
		peck_assert_no_ei_events(ei);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_passive_ei_device_start_stop_emulating)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE);

	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_SYNC);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSEAT);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_RESUMED);

	peck_dispatch_until_stable(peck);

	uint32_t sequence = 123;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_start_emulating(device, sequence);
		eis_device_stop_emulating(device);
	}
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
		_unref_(ei_event) *stop =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_STOP_EMULATING);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_passive_ei_device_stop_emulating_when_removing)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE);

	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_SYNC);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSEAT);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_RESUMED);

	peck_dispatch_until_stable(peck);

	uint32_t sequence = 123;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_start_emulating(device, sequence);
		eis_device_remove(device);
	}
	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
		_unref_(ei_event) *stop =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_STOP_EMULATING);
		_unref_(ei_event) *close =
			peck_ei_next_event(ei, 	EI_EVENT_DEVICE_REMOVED);
	}

	return MUNIT_OK;
}

/* Same as test_ei_device_button_button() but for a passive context */
MUNIT_TEST(test_passive_ei_device_button_button)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 123;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_start_emulating(device, sequence);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
	}

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_button_button(device, BTN_LEFT, true);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_button_button(device, BTN_RIGHT, true);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_button_button(device, BTN_RIGHT, false);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_button_button(device, BTN_LEFT, false);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *ld =
			peck_ei_next_event(ei, EI_EVENT_BUTTON_BUTTON);
		munit_assert_int(ei_event_button_get_button(ld), ==, BTN_LEFT);
		munit_assert_true(ei_event_button_get_is_press(ld));

		_unref_(ei_event) *rd =
			peck_ei_next_event(ei, EI_EVENT_BUTTON_BUTTON);
		munit_assert_int(ei_event_button_get_button(rd), ==, BTN_RIGHT);
		munit_assert_true(ei_event_button_get_is_press(rd));

		_unref_(ei_event) *ru =
			peck_ei_next_event(ei, EI_EVENT_BUTTON_BUTTON);
		munit_assert_int(ei_event_button_get_button(ru), ==, BTN_RIGHT);
		munit_assert_false(ei_event_button_get_is_press(ru));

		_unref_(ei_event) *lu =
			peck_ei_next_event(ei, EI_EVENT_BUTTON_BUTTON);
		munit_assert_int(ei_event_button_get_button(lu), ==, BTN_LEFT);
		munit_assert_false(ei_event_button_get_is_press(lu));

	}

	return MUNIT_OK;
}

/* Same as test_passive_ei_device_pointer_rel() but for a passive context */
MUNIT_TEST(test_passive_ei_device_keyboard_key)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 123;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_keyboard(peck);
		eis_device_start_emulating(device, sequence);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
	}

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_keyboard(peck);
		eis_device_keyboard_key(device, KEY_Q, true);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_keyboard_key(device, KEY_Q, false);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *press =
			peck_ei_next_event(ei, EI_EVENT_KEYBOARD_KEY);
		munit_assert_int(ei_event_keyboard_get_key(press), ==, KEY_Q);
		munit_assert_true(ei_event_keyboard_get_key_is_press(press));

		_unref_(ei_event) *release =
			peck_ei_next_event(ei, EI_EVENT_KEYBOARD_KEY);
		munit_assert_int(ei_event_keyboard_get_key(release), ==, KEY_Q);
		munit_assert_false(ei_event_keyboard_get_key_is_press(release));
	}

	return MUNIT_OK;
}

/* Same as test_ei_device_pointer_rel() but for a passive context */
MUNIT_TEST(test_passive_ei_device_pointer_rel)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 1234;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_start_emulating(device, sequence);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
	}

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_pointer_motion(device, 1, 2);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_pointer_motion(device, 0.3, 1.4);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_pointer_motion(device, 100, 200);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *first =
			peck_ei_next_event(ei, EI_EVENT_POINTER_MOTION);
		munit_assert_double_equal(ei_event_pointer_get_dx(first), 1.0, 2 /* precision */);
		munit_assert_double_equal(ei_event_pointer_get_dy(first), 2.0, 2 /* precision */);

		_unref_(ei_event) *second =
			peck_ei_next_event(ei, EI_EVENT_POINTER_MOTION);
		munit_assert_double_equal(ei_event_pointer_get_dx(second), 0.3, 2 /* precision */);
		munit_assert_double_equal(ei_event_pointer_get_dy(second), 1.4, 2 /* precision */);

		_unref_(ei_event) *third =
			peck_ei_next_event(ei, EI_EVENT_POINTER_MOTION);
		munit_assert_double_equal(ei_event_pointer_get_dx(third), 100, 2 /* precision */);
		munit_assert_double_equal(ei_event_pointer_get_dy(third), 200, 2 /* precision */);
	}

	return MUNIT_OK;
}

/* Same as test_ei_device_pointer_abs() but for a passive context */
MUNIT_TEST(test_passive_ei_device_pointer_abs)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);
	struct eis_device *device = NULL;

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 123;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer_absolute(peck);
		eis_device_start_emulating(device, sequence);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
	}

	with_server(peck) {
		device = peck_eis_get_default_pointer_absolute(peck);

		for (int i = 0; i < 10; i++) {
			eis_device_pointer_motion_absolute(device, 1 * i , 2 + i);
			eis_device_frame(device, peck_eis_now(peck));
		}
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		for (int i = 0; i < 10; i++) {
			_unref_(ei_event) *e =
				peck_ei_next_event(ei, EI_EVENT_POINTER_MOTION_ABSOLUTE);
			munit_assert_double_equal(ei_event_pointer_get_absolute_x(e), 1.0 * i, 2 /* precision */);
			munit_assert_double_equal(ei_event_pointer_get_absolute_y(e), 2.0 + i, 2 /* precision */);
		}
		peck_assert_no_ei_events(ei);
	}

	with_server(peck) {
		/* We know our default device has one region */
		struct eis_region *r = eis_device_get_region(device, 0);
		uint32_t maxx = eis_region_get_x(r) + eis_region_get_width(r);
		uint32_t maxy = eis_region_get_y(r) + eis_region_get_height(r);

		/* outside of pointer range, expect to be discarded */
		eis_device_pointer_motion_absolute(device, maxx + 1, maxy/2.0);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_pointer_motion_absolute(device, maxx/2.0 , maxy + 1);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		peck_assert_no_ei_events(ei);
	}

	with_server(peck) {
		eis_device_remove(device);
		/* absmotion after remove must not trigger an event */
		eis_device_pointer_motion_absolute(device, 100, 200);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *stop =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_STOP_EMULATING);
		_unref_(ei_event) *closed =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED);
		peck_assert_no_ei_events(ei);
	}

	return MUNIT_OK;
}

/* same as test_ei_device_scroll_delta but for a passive context */
MUNIT_TEST(test_passive_ei_device_scroll_delta)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 123;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_start_emulating(device, sequence);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
	}

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_scroll_delta(device, 1.1, 2.2);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_scroll_discrete(device, 3, 4);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *first =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA);
		munit_assert_double_equal(ei_event_scroll_get_dx(first), 1.1, 2 /* precision */);
		munit_assert_double_equal(ei_event_scroll_get_dy(first), 2.2, 2 /* precision */);

		_unref_(ei_event) *second =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_DISCRETE);
		munit_assert_int(ei_event_scroll_get_discrete_dx(second), ==, 3);
		munit_assert_int(ei_event_scroll_get_discrete_dy(second), ==, 4);
	}

	return MUNIT_OK;
}

/* same as test_ei_device_scroll_stop but for a passive context */
MUNIT_TEST(test_passive_ei_device_scroll_stop)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 456;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_start_emulating(device, sequence);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
	}

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_scroll_delta(device, 1.1, 2.2);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_scroll_stop(device, true, false);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_scroll_stop(device, false, true);
		eis_device_frame(device, peck_eis_now(peck));

		/* This should not generate an event */
		eis_device_scroll_stop(device, true, true);
		eis_device_frame(device, peck_eis_now(peck));

		/* But scrolling again will re-enable stopping */
		eis_device_scroll_delta(device, 3.3, 4.4);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_scroll_stop(device, true, true);
		eis_device_frame(device, peck_eis_now(peck));

		eis_device_scroll_delta(device, 3.3, 4.4);
		/* This one is a client bug and shouldn't trigger an event */
		eis_device_scroll_stop(device, false, false);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *scroll =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA);

		_unref_(ei_event) *first =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_STOP);
		munit_assert(ei_event_scroll_get_stop_x(first));
		munit_assert(!ei_event_scroll_get_stop_y(first));

		_unref_(ei_event) *second =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_STOP);
		munit_assert(!ei_event_scroll_get_stop_x(second));
		munit_assert(ei_event_scroll_get_stop_y(second));

		/* third one doesn't trigger an event */

		_unref_(ei_event) *again =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA);

		_unref_(ei_event) *fourth =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_STOP);
		munit_assert(ei_event_scroll_get_stop_x(fourth));
		munit_assert(ei_event_scroll_get_stop_y(fourth));

		_unref_(ei_event) *again_again =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA);

		peck_assert_no_ei_events(ei);
	}

	return MUNIT_OK;
}

/* same as test_ei_device_scroll_cancel but for a passive context */
MUNIT_TEST(test_passive_ei_device_scroll_cancel)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 546;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_start_emulating(device, sequence);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
	}

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_scroll_delta(device, 1.1, 2.2);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_scroll_cancel(device, true, false);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_scroll_cancel(device, false, true);
		eis_device_frame(device, peck_eis_now(peck));

		/* This should not generate an event */
		eis_device_scroll_cancel(device, true, true);
		eis_device_frame(device, peck_eis_now(peck));

		/* But scrolling again will re-enable stopping */
		eis_device_scroll_delta(device, 3.3, 4.4);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_scroll_cancel(device, true, true);
		eis_device_frame(device, peck_eis_now(peck));

		eis_device_scroll_delta(device, 3.3, 4.4);
		/* This one is a client bug and shouldn't trigger an event */
		eis_device_scroll_cancel(device, false, false);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *scroll =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA);

		_unref_(ei_event) *first =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_CANCEL);
		munit_assert(ei_event_scroll_get_stop_x(first));
		munit_assert(!ei_event_scroll_get_stop_y(first));

		_unref_(ei_event) *second =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_CANCEL);
		munit_assert(!ei_event_scroll_get_stop_x(second));
		munit_assert(ei_event_scroll_get_stop_y(second));

		/* third one doesn't trigger an event */

		_unref_(ei_event) *again =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA);

		_unref_(ei_event) *fourth =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_CANCEL);
		munit_assert(ei_event_scroll_get_stop_x(fourth));
		munit_assert(ei_event_scroll_get_stop_y(fourth));

		_unref_(ei_event) *again_again =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA);

		peck_assert_no_ei_events(ei);
	}

	return MUNIT_OK;
}

/* same as test_ei_device_scroll_stop_cancel but for a passive context */
MUNIT_TEST(test_passive_ei_device_scroll_stop_cancel)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 456;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_start_emulating(device, sequence);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
	}

	/* cancel after stop is fine, stop after cancel is ignored */
	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_pointer(peck);
		eis_device_scroll_delta(device, 1.1, 2.2);
		eis_device_frame(device, peck_eis_now(peck));
		peck_mark(peck);
		eis_device_scroll_stop(device, true, false);
		peck_mark(peck);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_scroll_cancel(device, true, false);
		peck_mark(peck);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_scroll_cancel(device, false, true);
		eis_device_frame(device, peck_eis_now(peck));
		peck_mark(peck);

		/* This should not generate an event */
		eis_device_scroll_stop(device, true, true);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *scroll =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA);

		_unref_(ei_event) *stop =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_STOP);
		munit_assert(ei_event_scroll_get_stop_x(stop));
		munit_assert(!ei_event_scroll_get_stop_y(stop));

		_unref_(ei_event) *first =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_CANCEL);
		munit_assert(ei_event_scroll_get_stop_x(first));
		munit_assert(!ei_event_scroll_get_stop_y(first));

		_unref_(ei_event) *second =
			peck_ei_next_event(ei, EI_EVENT_SCROLL_CANCEL);
		munit_assert(!ei_event_scroll_get_stop_x(second));
		munit_assert(ei_event_scroll_get_stop_y(second));

		/* third one doesn't trigger an event */
		peck_assert_no_ei_events(ei);
	}

	return MUNIT_OK;
}

/* same as test_ei_device_touch but for a passive context */
MUNIT_TEST(test_passive_ei_device_touch)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);
	struct eis_device *device = NULL;
	uint32_t maxx = 0, maxy = 0;

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 123;

	with_server(peck) {
		device = peck_eis_get_default_touch(peck);
		eis_device_start_emulating(device, sequence);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
	}

	with_server(peck) {
		device = peck_eis_get_default_touch(peck);
		/* We know our default device has one region */

		struct eis_region *r = eis_device_get_region(device, 0);
		maxx = eis_region_get_x(r) + eis_region_get_width(r);
		maxy = eis_region_get_y(r) + eis_region_get_height(r);

		eis_device_start_emulating(device, ++sequence);

		_unref_(eis_touch) *t = eis_device_touch_new(device);
		eis_touch_down(t, 1, 2);
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_motion(t, 200, 500);
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_up(t);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *down = peck_ei_touch_down(ei, 1, 2);
		uint32_t tid = ei_event_touch_get_id(down);

		_unref_(ei_event) *motion = peck_ei_touch_motion(ei, 200, 500);
		munit_assert_uint32(ei_event_touch_get_id(motion), ==, tid);

		_unref_(ei_event) *up = peck_ei_touch_up(ei);
		munit_assert_uint32(ei_event_touch_get_id(up), ==, tid);
		peck_assert_no_ei_events(ei);
	}

	with_server(peck) {
		_unref_(eis_touch) *t = eis_device_touch_new(device);
		/* outside clip range, expect touch to be dropped */
		with_nonfatal_eis_bug(peck)
			eis_touch_down(t, maxx + 1, maxy/2);
		eis_device_frame(device, peck_eis_now(peck));
		/* ignored because the touch down was out of range */
		eis_touch_motion(t, maxx + 1, maxy/3);
		eis_device_frame(device, peck_eis_now(peck));
		/* ignored because the touch down was out of range */
		with_nonfatal_eis_bug(peck)
			eis_touch_up(t);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		_unref_(eis_touch) *t = eis_device_touch_new(device);
		/* outside clip range, expect touch to be dropped */
		with_nonfatal_eis_bug(peck)
			eis_touch_down(t, maxx/2, maxy + 1);
		eis_device_frame(device, peck_eis_now(peck));
		/* ignored because the touch down was out of range */
		eis_touch_motion(t, maxx/3, maxy + 1);
		eis_device_frame(device, peck_eis_now(peck));
		/* ignored because the touch down was out of range */
		with_nonfatal_eis_bug(peck)
			eis_touch_up(t);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		peck_assert_no_ei_events(ei);
	}

	with_server(peck) {
		_unref_(eis_touch) *t = eis_device_touch_new(device);
		eis_touch_down(t, 100, 200);
		eis_device_frame(device, peck_eis_now(peck));
		/* outside allowed range, generates a touch up */
		with_nonfatal_eis_bug(peck)
			eis_touch_motion(t, maxx + 1, 200);
		eis_device_frame(device, peck_eis_now(peck));
		/* touch is already considered up */
		with_nonfatal_eis_bug(peck)
			eis_touch_up(t);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 200);
		_unref_(ei_event) *up = peck_ei_touch_up(ei);
		peck_assert_no_ei_events(ei);
	}

	with_server(peck) {
		_unref_(eis_touch) *t = eis_device_touch_new(device);
		eis_touch_down(t, 100, 100);
		eis_device_frame(device, peck_eis_now(peck));
		/* client forgets to touch up, touch_unref takes care of it */
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 100);
		_unref_(ei_event) *up = peck_ei_touch_up(ei);
		peck_assert_no_ei_events(ei);
	}

	with_server(peck) {
		/* touch only allocated, not actually set down */
		struct eis_touch *t1 = eis_device_touch_new(device);

		/* touch never set down */
		_unref_(eis_touch) *t2 = eis_device_touch_new(device);
		with_nonfatal_eis_bug(peck)
			eis_touch_up(t2);
		eis_device_frame(device, peck_eis_now(peck));

		/* touch never set down */
		_unref_(eis_touch) *t3 = eis_device_touch_new(device);
		with_nonfatal_eis_bug(peck)
			eis_touch_motion(t3, 100, 200);
		eis_device_frame(device, peck_eis_now(peck));
		with_nonfatal_eis_bug(peck)
			eis_touch_up(t3);
		eis_device_frame(device, peck_eis_now(peck));

		with_nonfatal_eis_bug(peck)
			eis_touch_unref(t1);
	}

	peck_dispatch_until_stable(peck);
	with_client(peck) {
		peck_assert_no_ei_events(ei);
	}

	with_server(peck) {
		/* touch re-used */
		_unref_(eis_touch) *t = eis_device_touch_new(device);
		eis_touch_down(t, 100, 200);
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_up(t);
		eis_device_frame(device, peck_eis_now(peck));
		with_nonfatal_eis_bug(peck)
			eis_touch_down(t, 200, 300);
		eis_device_frame(device, peck_eis_now(peck));
		with_nonfatal_eis_bug(peck)
			eis_touch_motion(t, 300, 400);
		eis_device_frame(device, peck_eis_now(peck));
		with_nonfatal_eis_bug(peck)
			eis_touch_up(t);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 200);
		_unref_(ei_event) *up = peck_ei_touch_up(ei);
		peck_assert_no_ei_events(ei);
	}

	with_server(peck) {
		/* double-down, double-up */
		_unref_(eis_touch) *t = eis_device_touch_new(device);
		eis_touch_down(t, 100, 200);
		eis_device_frame(device, peck_eis_now(peck));
		with_nonfatal_eis_bug(peck)
			eis_touch_down(t, 200, 300); /* ignored */
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_motion(t, 300, 400);
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_up(t);
		eis_device_frame(device, peck_eis_now(peck));
		with_nonfatal_eis_bug(peck)
			eis_touch_up(t); /* ignored */
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 200);
		_unref_(ei_event) *motion = peck_ei_touch_motion(ei, 300, 400);
		_unref_(ei_event) *up = peck_ei_touch_up(ei);
		peck_assert_no_ei_events(ei);
	}

	return MUNIT_OK;
}

/* same as test_ei_device_touch_cancel but for a passive context */
MUNIT_TEST(test_passive_ei_device_touch_cancel)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);
	struct eis_device *device = NULL;

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 123;

	with_server(peck) {
		device = peck_eis_get_default_touch(peck);
		eis_device_start_emulating(device, sequence);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
	}

	with_server(peck) {
		device = peck_eis_get_default_touch(peck);

		_unref_(eis_touch) *t = eis_device_touch_new(device);
		eis_touch_down(t, 1, 2);
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_motion(t, 200, 500);
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_cancel(t);
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *down = peck_ei_touch_down(ei, 1, 2);
		uint32_t tid = ei_event_touch_get_id(down);

		_unref_(ei_event) *motion = peck_ei_touch_motion(ei, 200, 500);
		munit_assert_uint32(ei_event_touch_get_id(motion), ==, tid);

		_unref_(ei_event) *up = peck_ei_touch_up(ei);
		munit_assert_uint32(ei_event_touch_get_id(up), ==, tid);
		munit_assert_true(ei_event_touch_get_is_cancel(up));
		peck_assert_no_ei_events(ei);
	}

	peck_dispatch_until_stable(peck);

	with_server(peck) {
		/* up + cancel, latter is ignored */
		_unref_(eis_touch) *t = eis_device_touch_new(device);
		eis_touch_down(t, 100, 200);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_motion(t, 300, 400);
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_up(t);
		eis_device_frame(device, peck_eis_now(peck));
		with_nonfatal_eis_bug(peck)
			eis_touch_cancel(t); /* ignored */
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 200);
		_unref_(ei_event) *motion = peck_ei_touch_motion(ei, 300, 400);
		_unref_(ei_event) *up = peck_ei_touch_up(ei);
		munit_assert_false(ei_event_touch_get_is_cancel(up));
		peck_assert_no_ei_events(ei);
	}

	with_server(peck) {
		/* cancel + up, latter is ignored */
		_unref_(eis_touch) *t = eis_device_touch_new(device);
		eis_touch_down(t, 100, 200);
		eis_device_frame(device, peck_eis_now(peck));
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_motion(t, 300, 400);
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_cancel(t);
		eis_device_frame(device, peck_eis_now(peck));
		with_nonfatal_eis_bug(peck)
			eis_touch_up(t); /* ignored */
		eis_device_frame(device, peck_eis_now(peck));
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 200);
		_unref_(ei_event) *motion = peck_ei_touch_motion(ei, 300, 400);
		_unref_(ei_event) *up = peck_ei_touch_up(ei);
		munit_assert_true(ei_event_touch_get_is_cancel(up));
		peck_assert_no_ei_events(ei);
	}

	return MUNIT_OK;
}

/* same as test_ei_device_multitouch but for a passive context */
MUNIT_TEST(test_passive_ei_device_multitouch)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	peck_dispatch_until_stable(peck);

	uint32_t sequence = 345;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_touch(peck);
		_unref_(eis_touch) *t1 = eis_device_touch_new(device);
		_unref_(eis_touch) *t2 = eis_device_touch_new(device);
		eis_device_start_emulating(device, sequence);
		eis_touch_down(t1, 1, 2);
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_motion(t1, 2, 3);
		eis_device_frame(device, peck_eis_now(peck));

		eis_touch_down(t2, 3, 4);
		eis_device_frame(device, peck_eis_now(peck));
		eis_touch_motion(t2, 4, 5);
		eis_device_frame(device, peck_eis_now(peck));

		eis_touch_up(t2);
		eis_touch_up(t1);
		eis_device_frame(device, peck_eis_now(peck));

		eis_device_stop_emulating(device);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);

		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);

		_unref_(ei_event) *down1 = peck_ei_touch_down(ei, 1, 2);
		uint32_t tid1 = ei_event_touch_get_id(down1);

		_unref_(ei_event) *motion1 = peck_ei_touch_motion(ei, 2, 3);
		munit_assert_uint32(ei_event_touch_get_id(motion1), ==, tid1);

		_unref_(ei_event) *down2 = peck_ei_touch_down(ei, 3, 4);
		uint32_t tid2 = ei_event_touch_get_id(down2);
		munit_assert_uint32(tid2, !=, tid1);

		_unref_(ei_event) *motion2 = peck_ei_touch_motion(ei, 4, 5);
		munit_assert_uint32(ei_event_touch_get_id(motion2), ==, tid2);

		_unref_(ei_event) *up2 = peck_ei_touch_up(ei);
		munit_assert_uint32(ei_event_touch_get_id(up2), ==, tid2);

		_unref_(ei_event) *up1 = peck_ei_touch_up(ei);
		munit_assert_uint32(ei_event_touch_get_id(up1), ==, tid1);

		_unref_(ei_event) *stop =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_STOP_EMULATING);
	}

	return MUNIT_OK;
}

MUNIT_TEST(test_passive_ei_frame_timestamp)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);
	uint64_t ts1 = 0, ts2 = 0;

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_SYNC);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 345;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_keyboard(peck);
		eis_device_start_emulating(device, sequence);

		ts1 = peck_eis_now(peck);
		eis_device_keyboard_key(device, KEY_Q, true);
		eis_device_frame(device, ts1);

		ts2 = peck_eis_now(peck);
		eis_device_keyboard_key(device, KEY_Q, false);
		eis_device_frame(device, ts2);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
		_unref_(ei_event) *kbd1 =
			peck_ei_next_event(ei, EI_EVENT_KEYBOARD_KEY);
		_unref_(ei_event) *frame1 =
			peck_ei_next_event(ei, EI_EVENT_FRAME);
		_unref_(ei_event) *kbd2 =
			peck_ei_next_event(ei, EI_EVENT_KEYBOARD_KEY);
		_unref_(ei_event) *frame2 =
			peck_ei_next_event(ei, EI_EVENT_FRAME);

		uint64_t timestamp = ei_event_get_time(frame1);
		munit_assert_uint64(timestamp, ==, ts1);
		munit_assert_uint64(timestamp, ==, ei_event_get_time(kbd1));

		timestamp = ei_event_get_time(frame2);
		munit_assert_uint64(timestamp, ==, ts2);
		munit_assert_uint64(timestamp, ==, ei_event_get_time(kbd2));

		peck_assert_no_ei_events(ei);
	}

	return MUNIT_OK;
}


MUNIT_TEST(test_passive_ei_flush_frame)
{
	_unref_(peck) *peck = peck_new_context("mode", PECK_EI_RECEIVER);

	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_SYNC);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE);
	peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_SYNC);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT);
	peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES);
	peck_dispatch_until_stable(peck);

	uint32_t sequence = 678;

	with_server(peck) {
		struct eis_device *device = peck_eis_get_default_keyboard(peck);
		eis_device_start_emulating(device, sequence);
		eis_device_keyboard_key(device, KEY_Q, true);
		/* Missing call to ei_device_frame() */
		with_nonfatal_eis_bug(peck)
			eis_device_stop_emulating(device);
	}

	peck_dispatch_until_stable(peck);

	with_client(peck) {
		_unref_(ei_event) *start =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING);
		munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence);
		_unref_(ei_event) *kbd =
			peck_ei_next_event(ei, EI_EVENT_KEYBOARD_KEY);
		_unref_(ei_event) *frame =
			peck_ei_next_event(ei, EI_EVENT_FRAME);
		_unref_(ei_event) *stop =
			peck_ei_next_event(ei, EI_EVENT_DEVICE_STOP_EMULATING);
		peck_assert_no_ei_events(ei);
	}

	return MUNIT_OK;
}
